#pragma warning(disable : 4244)
#include <fstream>
#include <iostream>
#include <Avalon/ServiceLocator/Account.hpp>
#include <Avalon/ServiceLocator/Directory.hpp>
#include <Avalon/ServiceLocator/DirectoryEntry.hpp>
#include <Avalon/ServiceLocator/ServiceChannelFactory.hpp>
#include <Avalon/ServiceLocator/ServiceLocatorClient.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>
#pragma warning(disable : 4003)
#include <libjson.h>
#pragma warning(default : 4003)

using namespace Avalon;
using namespace Avalon::IO;
using namespace Avalon::Serialization;
using namespace Avalon::ServiceLocator;
using namespace boost;
using namespace boost::posix_time;
using namespace libjson;
using namespace std;

namespace {
  vector<string> Tokenize(const string& input) {
    vector<string> result;
    split(result, input, is_any_of(" "));
    return result;
  }
}

int main(int argc, char** argv) {

  // Load the config file.
  JSONNode config;
  ifstream source("config.json");
  if(!source.good()) {
    cout << "config.json not found." << endl;
    return -1;
  }
  string configContents = string(istreambuf_iterator<char>(source),
    istreambuf_iterator<char>());
  if(!is_valid(configContents)) {
    cout << "JSON parsing error in config.json" << endl;
    return -1;
  }
  config = parse(configContents);

  // Connect to the service locator.
  Channel* serviceClientChannel;
  DataShuttleFactory* serviceClientDataShuttle;
  int timeout;
  string username;
  string password;
  try {
    ServiceChannelFactory channelFactory;
    JSONNode& serviceClientConfig = config["service_client"];
    serviceClientChannel = channelFactory.CreateChannel(
      serviceClientConfig["channel"].write());
    serviceClientDataShuttle = channelFactory.CreateDataShuttleFactory(
      serviceClientConfig["data_shuttle"].write());
    timeout = serviceClientConfig["timeout"].as_int();
    username = serviceClientConfig["username"].as_string();
    password = serviceClientConfig["password"].as_string();
  } catch(std::exception& e) {
    cout << e.what() << endl;
    return -1;
  }
  ServiceLocatorClient serviceClient(serviceClientChannel,
    serviceClientDataShuttle, seconds(timeout));
  cout << "Connecting to service locator: ";
  try {
    serviceClient.Login(username, password);
  } catch(std::exception& e) {
    cout << e.what() << endl;
    return -1;
  }
  cout << "Passed." << endl;
  Account* dummy = new Account(Account::Tag());
  delete dummy;
  Account* account;
  Directory* currentDirectory;
  try {
    account = static_cast<Account*>(serviceClient.LoadDirectoryEntry(
      serviceClient.GetAccountTag().m_id));
    currentDirectory = serviceClient.LoadParentDirectory(
      account->GetTag().m_id);
  } catch(std::exception& e) {
    cout << e.what() << endl;
    return -1;
  }
  cout << "Passed." << endl;
  while(!ReceivedKillEvent()) {
    cout << ">>> ";
    string input;
    getline(cin, input);
    vector<string> tokens(Tokenize(input));
    if(tokens[0] == "mkacc") {
      try {
        serviceClient.CreateAccount(tokens[1], currentDirectory->GetTag(),
          tokens[2]);
        cout << "Done." << endl;
      } catch(std::exception& e) {
        cout << e.what() << endl;
      }
    } else if(tokens[0] == "mkdir") {
      try {
        serviceClient.CreateDirectory(tokens[1], currentDirectory->GetTag());
        cout << "Done." << endl;
      } catch(std::exception& e) {
        cout << e.what() << endl;
      }
    } else if(tokens[0] == "chmod") {
      try {
        DirectoryEntry::Tag account(DirectoryEntry::Type::ACCOUNT,
          lexical_cast<int>(tokens[1]), "");
        DirectoryEntry::Tag entry(DirectoryEntry::Type::NONE,
          lexical_cast<int>(tokens[2]), "");
        Directory::Permission permissions(lexical_cast<int>(tokens[3]));
        serviceClient.SetPermissions(account, entry, permissions);
        cout << "Done." << endl;
      } catch(std::exception& e) {
        cout << e.what() << endl;
      }
    } else if(tokens[0] == "mv") {
      try {
        serviceClient.MoveEntry(DirectoryEntry::Tag(DirectoryEntry::Type::NONE,
          lexical_cast<int>(tokens[1]), ""),
          DirectoryEntry::Tag(DirectoryEntry::Type::NONE,
          lexical_cast<int>(tokens[2]), ""));
        cout << "Done." << endl;
      } catch(std::exception& e) {
        cout << e.what() << endl;
      }
    } else if(tokens[0] == "cd") {
      try {
        currentDirectory = static_cast<Directory*>(
          serviceClient.LoadDirectoryEntry(lexical_cast<int>(tokens[1])));
        cout << "Done." << endl;
      } catch(std::exception& e) {
        cout << e.what() << endl;
      }
    } else if(tokens[0] == "dir") {
      vector<DirectoryEntry::Tag> tags = currentDirectory->GetEntries();
      for(vector<DirectoryEntry::Tag>::const_iterator i = tags.begin();
          i != tags.end(); ++i) {
        string type;
        if(i->m_type == DirectoryEntry::Type::DIRECTORY) {
          type = "<DIR>";
        } else {
          type = "";
        }
        cout << "\t" << type << "\t" << i->m_id << "\t" << i->m_name << endl;
      }
    } else if(tokens[0] == "del") {
      try {
        serviceClient.DeleteEntry(
          DirectoryEntry::Tag(DirectoryEntry::Type::NONE, lexical_cast<int>(
          tokens[1]), ""));
        cout << "Done." << endl;
      } catch(std::exception& e) {
        cout << e.what() << endl;
      }
    } else if(tokens[0] == "locate") {
      vector<ServiceEntry> services = serviceClient.Locate(tokens[1]);
      for(vector<ServiceEntry>::const_iterator i = services.begin();
          i != services.end(); ++i) {
        cout << i->GetName() << " " << i->GetId() << "\n";
        cout << i->GetAccount().m_name << " " << i->GetAccount().m_id << "\n";
        cout << i->GetDetails() << endl;
      }
    } else if(tokens[0] == "exit") {
      break;
    } else {
      cout << "Unknown command." << endl;
    }
  }
}
